@@ -1,4 +1,6 @@ |
||
1 | 1 |
class ScenariosController < ApplicationController |
2 |
+ skip_before_filter :authenticate_user!, :only => :export |
|
3 |
+ |
|
2 | 4 |
def index |
3 | 5 |
@scenarios = current_user.scenarios.page(params[:page]) |
4 | 6 |
|
@@ -27,10 +29,8 @@ class ScenariosController < ApplicationController |
||
27 | 29 |
end |
28 | 30 |
end |
29 | 31 |
|
30 |
- # Share is a work in progress! |
|
31 | 32 |
def share |
32 | 33 |
@scenario = current_user.scenarios.find(params[:id]) |
33 |
- @agents = @scenario.agents.preload(:scenarios).page(params[:page]) |
|
34 | 34 |
|
35 | 35 |
respond_to do |format| |
36 | 36 |
format.html |
@@ -38,6 +38,19 @@ class ScenariosController < ApplicationController |
||
38 | 38 |
end |
39 | 39 |
end |
40 | 40 |
|
41 |
+ def export |
|
42 |
+ @scenario = Scenario.find(params[:id]) |
|
43 |
+ raise ActiveRecord::RecordNotFound unless @scenario.public? || (current_user && current_user.id == @scenario.user_id) |
|
44 |
+ |
|
45 |
+ @exporter = AgentsExporter.new(:name => @scenario.name, |
|
46 |
+ :description => @scenario.description, |
|
47 |
+ :guid => @scenario.guid, |
|
48 |
+ :source_url => @scenario.public? && export_scenario_url(@scenario), |
|
49 |
+ :agents => @scenario.agents) |
|
50 |
+ response.headers['Content-Disposition'] = 'attachment; filename="' + @exporter.filename + '"' |
|
51 |
+ render :json => JSON.pretty_generate(@exporter.as_json) |
|
52 |
+ end |
|
53 |
+ |
|
41 | 54 |
def edit |
42 | 55 |
@scenario = current_user.scenarios.find(params[:id]) |
43 | 56 |
|
@@ -1,16 +1,22 @@ |
||
1 | 1 |
class Scenario < ActiveRecord::Base |
2 |
- attr_accessible :name, :agent_ids |
|
2 |
+ attr_accessible :name, :agent_ids, :description, :public |
|
3 | 3 |
|
4 | 4 |
belongs_to :user, :counter_cache => :scenario_count, :inverse_of => :scenarios |
5 | 5 |
has_many :scenario_memberships, :dependent => :destroy, :inverse_of => :scenario |
6 | 6 |
has_many :agents, :through => :scenario_memberships, :inverse_of => :scenarios |
7 | 7 |
|
8 |
+ before_save :make_guid |
|
9 |
+ |
|
8 | 10 |
validates_presence_of :name, :user |
9 | 11 |
|
10 | 12 |
validate :agents_are_owned |
11 | 13 |
|
12 | 14 |
protected |
13 | 15 |
|
16 |
+ def make_guid |
|
17 |
+ self.guid = SecureRandom.hex unless guid.present? |
|
18 |
+ end |
|
19 |
+ |
|
14 | 20 |
def agents_are_owned |
15 | 21 |
errors.add(:agents, "must be owned by you") unless agents.all? {|s| s.user == user } |
16 | 22 |
end |
@@ -12,12 +12,29 @@ |
||
12 | 12 |
<div class="col-md-4"> |
13 | 13 |
<div class="form-group"> |
14 | 14 |
<%= f.label :name %> |
15 |
- <%= f.text_field :name, :class => 'form-control' %> |
|
15 |
+ <%= f.text_field :name, :class => 'form-control', :placeholder => "Name your Scenario" %> |
|
16 | 16 |
</div> |
17 | 17 |
</div> |
18 | 18 |
</div> |
19 | 19 |
|
20 | 20 |
<div class="row"> |
21 |
+ <div class="col-md-8"> |
|
22 |
+ <div class="form-group"> |
|
23 |
+ <%= f.label :description, "Optional Description" %> |
|
24 |
+ <%= f.text_area :description, :rows => 10, :class => 'form-control', :placeholder => "Optionally describe what this set of Agents will do" %> |
|
25 |
+ </div> |
|
26 |
+ |
|
27 |
+ <div class="checkbox"> |
|
28 |
+ <%= f.label :public do %> |
|
29 |
+ <%= f.check_box :public %> Share this Scenario publicly |
|
30 |
+ <% end %> |
|
31 |
+ <span class="glyphicon glyphicon-question-sign hover-help" data-content="When selected, this Scenario and all Agents in it will be made public. An export URL will be available to share with other Huginn users. Be very careful that you do not have secret credentials stored in these Agents' options. Instead, use Credentials by reference."></span> |
|
32 |
+ </div> |
|
33 |
+ |
|
34 |
+ </div> |
|
35 |
+ </div> |
|
36 |
+ |
|
37 |
+ <div class="row"> |
|
21 | 38 |
<div class="col-md-4"> |
22 | 39 |
<div class="form-group"> |
23 | 40 |
<div> |
@@ -20,14 +20,16 @@ |
||
20 | 20 |
|
21 | 21 |
<% @scenarios.each do |scenario| %> |
22 | 22 |
<tr> |
23 |
- <td><span class='label label-info'><%= scenario.name %></span></td> |
|
23 |
+ <td> |
|
24 |
+ <%= link_to(scenario.name, scenario, class: "label label-info") %> |
|
25 |
+ </td> |
|
24 | 26 |
<td><%= link_to pluralize(scenario.agents.count, "agent"), scenario %></td> |
25 | 27 |
<td> |
26 | 28 |
<div class="btn-group btn-group-xs" style="float: right"> |
27 | 29 |
<%= link_to 'Show', scenario, class: "btn btn-default" %> |
28 | 30 |
<%= link_to 'Edit', edit_scenario_path(scenario), class: "btn btn-default" %> |
29 | 31 |
<%= link_to 'Share', share_scenario_path(scenario), class: "btn btn-default" %> |
30 |
- <%= link_to 'Delete', scenario_path(scenario), method: :delete, data: {confirm: 'Are you sure?'}, class: "btn btn-default" %> |
|
32 |
+ <%= link_to 'Delete', scenario_path(scenario), method: :delete, data: { confirm: "This will remove the '#{scenario.name}' Scenerio from all Agents and delete it. Are you sure?" }, class: "btn btn-default" %> |
|
31 | 33 |
</div> |
32 | 34 |
</td> |
33 | 35 |
</tr> |
@@ -5,6 +5,22 @@ |
||
5 | 5 |
<h2>Share <span class='label label-info scenario'><%= @scenario.name %></span> with the world</h2> |
6 | 6 |
</div> |
7 | 7 |
|
8 |
+ <p> |
|
9 |
+ <strong>Please be sure that none of the Agents in this Scenario have sensitive data in their settings before sharing!</strong> |
|
10 |
+ </p> |
|
11 |
+ |
|
12 |
+ <% if @scenario.public? %> |
|
13 |
+ <p> |
|
14 |
+ This Scenario is public. You can <%= link_to "download and share your export file", export_scenario_path(@scenario) %>, or give out this URL: |
|
15 |
+ </p> |
|
16 |
+ |
|
17 |
+ <form onsubmit='return false;'> |
|
18 |
+ <input type='text' class='form-control' value='<%= export_scenario_url(@scenario) %>' onclick="return this.select();"/> |
|
19 |
+ </form> |
|
20 |
+ <% else %> |
|
21 |
+ This Scenario is not public. You can share it by <%= link_to "downloading and sharing your export file", export_scenario_path(@scenario) %>. |
|
22 |
+ <% end %> |
|
23 |
+ |
|
8 | 24 |
<hr> |
9 | 25 |
|
10 | 26 |
<div class="row"> |
@@ -2,20 +2,19 @@ |
||
2 | 2 |
<div class='row'> |
3 | 3 |
<div class='col-md-12'> |
4 | 4 |
<div class="page-header"> |
5 |
- <h2>Scenario <span class='label label-info scenario'><%= @scenario.name %></span></h2> |
|
5 |
+ <h2><%= "Public" if @scenario.public? %> Scenario <span class='label label-info scenario'><%= @scenario.name %></span></h2> |
|
6 | 6 |
</div> |
7 | 7 |
|
8 |
+ <%= render 'agents/table', :returnTo => scenario_path(@scenario) %> |
|
9 |
+ |
|
10 |
+ <br/> |
|
11 |
+ |
|
8 | 12 |
<div class="btn-group"> |
9 | 13 |
<%= link_to '<span class="glyphicon glyphicon-chevron-left"></span> Back'.html_safe, scenarios_path, class: "btn btn-default" %> |
10 | 14 |
<%= link_to '<span class="glyphicon glyphicon-edit"></span> Edit'.html_safe, edit_scenario_path(@scenario), class: "btn btn-default" %> |
11 | 15 |
<%= link_to '<span class="glyphicon glyphicon-share-alt"></span> Share'.html_safe, share_scenario_path(@scenario), class: "btn btn-default" %> |
16 |
+ <%= link_to '<span class="glyphicon glyphicon-trash"></span> Delete'.html_safe, scenario_path(@scenario), method: :delete, data: { confirm: "This will remove the '#{@scenario.name}' Scenerio from all Agents and delete it. Are you sure?" }, class: "btn btn-default" %> |
|
12 | 17 |
</div> |
13 |
- |
|
14 |
- <div class="page-header"> |
|
15 |
- <h3>Agents</h3> |
|
16 |
- </div> |
|
17 |
- |
|
18 |
- <%= render 'agents/table', :returnTo => scenario_path(@scenario) %> |
|
19 | 18 |
</div> |
20 | 19 |
</div> |
21 | 20 |
</div> |
@@ -29,6 +29,7 @@ Huginn::Application.routes.draw do |
||
29 | 29 |
resources :scenarios do |
30 | 30 |
member do |
31 | 31 |
get :share |
32 |
+ get :export |
|
32 | 33 |
end |
33 | 34 |
end |
34 | 35 |
|
@@ -0,0 +1,8 @@ |
||
1 |
+class AddFieldsToScenarios < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ add_column :scenarios, :description, :text |
|
4 |
+ add_column :scenarios, :public, :boolean, :default => false, :null => false |
|
5 |
+ add_column :scenarios, :guid, :string, :null => false |
|
6 |
+ add_column :scenarios, :source_url, :string |
|
7 |
+ end |
|
8 |
+end |
@@ -9,23 +9,23 @@ |
||
9 | 9 |
# from scratch. The latter is a flawed and unsustainable approach (the more migrations |
10 | 10 |
# you'll amass, the slower it'll run and the greater likelihood for issues). |
11 | 11 |
# |
12 |
-# It's strongly recommended to check this file into your version control system. |
|
12 |
+# It's strongly recommended that you check this file into your version control system. |
|
13 | 13 |
|
14 |
-ActiveRecord::Schema.define(:version => 20140408150825) do |
|
14 |
+ActiveRecord::Schema.define(version: 20140531232016) do |
|
15 | 15 |
|
16 |
- create_table "agent_logs", :force => true do |t| |
|
17 |
- t.integer "agent_id", :null => false |
|
18 |
- t.text "message", :null => false |
|
19 |
- t.integer "level", :default => 3, :null => false |
|
16 |
+ create_table "agent_logs", force: true do |t| |
|
17 |
+ t.integer "agent_id", null: false |
|
18 |
+ t.text "message", limit: 16777215, null: false |
|
19 |
+ t.integer "level", default: 3, null: false |
|
20 | 20 |
t.integer "inbound_event_id" |
21 | 21 |
t.integer "outbound_event_id" |
22 |
- t.datetime "created_at", :null => false |
|
23 |
- t.datetime "updated_at", :null => false |
|
22 |
+ t.datetime "created_at", null: false |
|
23 |
+ t.datetime "updated_at", null: false |
|
24 | 24 |
end |
25 | 25 |
|
26 |
- create_table "agents", :force => true do |t| |
|
26 |
+ create_table "agents", force: true do |t| |
|
27 | 27 |
t.integer "user_id" |
28 |
- t.text "options" |
|
28 |
+ t.text "options", limit: 16777215 |
|
29 | 29 |
t.string "type" |
30 | 30 |
t.string "name" |
31 | 31 |
t.string "schedule" |
@@ -33,73 +33,62 @@ ActiveRecord::Schema.define(:version => 20140408150825) do |
||
33 | 33 |
t.datetime "last_check_at" |
34 | 34 |
t.datetime "last_receive_at" |
35 | 35 |
t.integer "last_checked_event_id" |
36 |
- t.datetime "created_at", :null => false |
|
37 |
- t.datetime "updated_at", :null => false |
|
38 |
- t.text "memory", :limit => 2147483647 |
|
36 |
+ t.datetime "created_at", null: false |
|
37 |
+ t.datetime "updated_at", null: false |
|
38 |
+ t.text "memory", limit: 2147483647 |
|
39 | 39 |
t.datetime "last_web_request_at" |
40 |
- t.integer "keep_events_for", :default => 0, :null => false |
|
41 | 40 |
t.datetime "last_event_at" |
42 | 41 |
t.datetime "last_error_log_at" |
43 |
- t.boolean "propagate_immediately", :default => false, :null => false |
|
44 |
- t.boolean "disabled", :default => false, :null => false |
|
42 |
+ t.integer "keep_events_for", default: 0, null: false |
|
43 |
+ t.boolean "propagate_immediately", default: false, null: false |
|
44 |
+ t.boolean "disabled", default: false, null: false |
|
45 | 45 |
end |
46 | 46 |
|
47 |
- add_index "agents", ["schedule"], :name => "index_agents_on_schedule" |
|
48 |
- add_index "agents", ["type"], :name => "index_agents_on_type" |
|
49 |
- add_index "agents", ["user_id", "created_at"], :name => "index_agents_on_user_id_and_created_at" |
|
47 |
+ add_index "agents", ["schedule"], name: "index_agents_on_schedule", using: :btree |
|
48 |
+ add_index "agents", ["type"], name: "index_agents_on_type", using: :btree |
|
49 |
+ add_index "agents", ["user_id", "created_at"], name: "index_agents_on_user_id_and_created_at", using: :btree |
|
50 | 50 |
|
51 |
- create_table "delayed_jobs", :force => true do |t| |
|
52 |
- t.integer "priority", :default => 0 |
|
53 |
- t.integer "attempts", :default => 0 |
|
54 |
- t.text "handler", :limit => 16777215 |
|
55 |
- t.text "last_error" |
|
51 |
+ create_table "delayed_jobs", force: true do |t| |
|
52 |
+ t.integer "priority", default: 0 |
|
53 |
+ t.integer "attempts", default: 0 |
|
54 |
+ t.text "handler", limit: 16777215 |
|
55 |
+ t.text "last_error", limit: 16777215 |
|
56 | 56 |
t.datetime "run_at" |
57 | 57 |
t.datetime "locked_at" |
58 | 58 |
t.datetime "failed_at" |
59 | 59 |
t.string "locked_by" |
60 | 60 |
t.string "queue" |
61 |
- t.datetime "created_at", :null => false |
|
62 |
- t.datetime "updated_at", :null => false |
|
61 |
+ t.datetime "created_at", null: false |
|
62 |
+ t.datetime "updated_at", null: false |
|
63 | 63 |
end |
64 | 64 |
|
65 |
- add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" |
|
65 |
+ add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree |
|
66 | 66 |
|
67 |
- create_table "events", :force => true do |t| |
|
67 |
+ create_table "events", force: true do |t| |
|
68 | 68 |
t.integer "user_id" |
69 | 69 |
t.integer "agent_id" |
70 |
- t.decimal "lat", :precision => 15, :scale => 10 |
|
71 |
- t.decimal "lng", :precision => 15, :scale => 10 |
|
72 |
- t.text "payload", :limit => 16777215 |
|
73 |
- t.datetime "created_at", :null => false |
|
74 |
- t.datetime "updated_at", :null => false |
|
70 |
+ t.decimal "lat", precision: 15, scale: 10 |
|
71 |
+ t.decimal "lng", precision: 15, scale: 10 |
|
72 |
+ t.text "payload", limit: 2147483647 |
|
73 |
+ t.datetime "created_at", null: false |
|
74 |
+ t.datetime "updated_at", null: false |
|
75 | 75 |
t.datetime "expires_at" |
76 | 76 |
end |
77 | 77 |
|
78 |
- add_index "events", ["agent_id", "created_at"], :name => "index_events_on_agent_id_and_created_at" |
|
79 |
- add_index "events", ["expires_at"], :name => "index_events_on_expires_at" |
|
80 |
- add_index "events", ["user_id", "created_at"], :name => "index_events_on_user_id_and_created_at" |
|
78 |
+ add_index "events", ["agent_id", "created_at"], name: "index_events_on_agent_id_and_created_at", using: :btree |
|
79 |
+ add_index "events", ["expires_at"], name: "index_events_on_expires_at", using: :btree |
|
80 |
+ add_index "events", ["user_id", "created_at"], name: "index_events_on_user_id_and_created_at", using: :btree |
|
81 | 81 |
|
82 |
- create_table "links", :force => true do |t| |
|
82 |
+ create_table "links", force: true do |t| |
|
83 | 83 |
t.integer "source_id" |
84 | 84 |
t.integer "receiver_id" |
85 |
- t.datetime "created_at", :null => false |
|
86 |
- t.datetime "updated_at", :null => false |
|
87 |
- t.integer "event_id_at_creation", :default => 0, :null => false |
|
85 |
+ t.datetime "created_at", null: false |
|
86 |
+ t.datetime "updated_at", null: false |
|
87 |
+ t.integer "event_id_at_creation", default: 0, null: false |
|
88 | 88 |
end |
89 | 89 |
|
90 |
- add_index "links", ["receiver_id", "source_id"], :name => "index_links_on_receiver_id_and_source_id" |
|
91 |
- add_index "links", ["source_id", "receiver_id"], :name => "index_links_on_source_id_and_receiver_id" |
|
92 |
- |
|
93 |
- create_table "user_credentials", :force => true do |t| |
|
94 |
- t.integer "user_id", :null => false |
|
95 |
- t.string "credential_name", :null => false |
|
96 |
- t.text "credential_value", :null => false |
|
97 |
- t.datetime "created_at", :null => false |
|
98 |
- t.datetime "updated_at", :null => false |
|
99 |
- t.string "mode", :default => "text", :null => false |
|
100 |
- end |
|
101 |
- |
|
102 |
- add_index "user_credentials", ["user_id", "credential_name"], :name => "index_user_credentials_on_user_id_and_credential_name", :unique => true |
|
90 |
+ add_index "links", ["receiver_id", "source_id"], name: "index_links_on_receiver_id_and_source_id", using: :btree |
|
91 |
+ add_index "links", ["source_id", "receiver_id"], name: "index_links_on_source_id_and_receiver_id", using: :btree |
|
103 | 92 |
|
104 | 93 |
create_table "scenario_memberships", force: true do |t| |
105 | 94 |
t.integer "agent_id", null: false |
@@ -109,37 +98,52 @@ ActiveRecord::Schema.define(:version => 20140408150825) do |
||
109 | 98 |
end |
110 | 99 |
|
111 | 100 |
create_table "scenarios", force: true do |t| |
112 |
- t.string "name", null: false |
|
113 |
- t.integer "user_id", null: false |
|
101 |
+ t.string "name", null: false |
|
102 |
+ t.integer "user_id", null: false |
|
114 | 103 |
t.datetime "created_at" |
115 | 104 |
t.datetime "updated_at" |
105 |
+ t.text "description" |
|
106 |
+ t.boolean "public", default: false, null: false |
|
107 |
+ t.string "guid", null: false |
|
108 |
+ t.string "source_url" |
|
116 | 109 |
end |
117 | 110 |
|
118 |
- create_table "users", :force => true do |t| |
|
119 |
- t.string "email", :default => "", :null => false |
|
120 |
- t.string "encrypted_password", :default => "", :null => false |
|
111 |
+ create_table "user_credentials", force: true do |t| |
|
112 |
+ t.integer "user_id", null: false |
|
113 |
+ t.string "credential_name", null: false |
|
114 |
+ t.text "credential_value", null: false |
|
115 |
+ t.datetime "created_at", null: false |
|
116 |
+ t.datetime "updated_at", null: false |
|
117 |
+ t.string "mode", default: "text", null: false |
|
118 |
+ end |
|
119 |
+ |
|
120 |
+ add_index "user_credentials", ["user_id", "credential_name"], name: "index_user_credentials_on_user_id_and_credential_name", unique: true, using: :btree |
|
121 |
+ |
|
122 |
+ create_table "users", force: true do |t| |
|
123 |
+ t.string "email", default: "", null: false |
|
124 |
+ t.string "encrypted_password", default: "", null: false |
|
121 | 125 |
t.string "reset_password_token" |
122 | 126 |
t.datetime "reset_password_sent_at" |
123 | 127 |
t.datetime "remember_created_at" |
124 |
- t.integer "sign_in_count", :default => 0 |
|
128 |
+ t.integer "sign_in_count", default: 0 |
|
125 | 129 |
t.datetime "current_sign_in_at" |
126 | 130 |
t.datetime "last_sign_in_at" |
127 | 131 |
t.string "current_sign_in_ip" |
128 | 132 |
t.string "last_sign_in_ip" |
129 |
- t.datetime "created_at", :null => false |
|
130 |
- t.datetime "updated_at", :null => false |
|
131 |
- t.boolean "admin", :default => false, :null => false |
|
132 |
- t.integer "failed_attempts", :default => 0 |
|
133 |
+ t.datetime "created_at", null: false |
|
134 |
+ t.datetime "updated_at", null: false |
|
135 |
+ t.boolean "admin", default: false, null: false |
|
136 |
+ t.integer "failed_attempts", default: 0 |
|
133 | 137 |
t.string "unlock_token" |
134 | 138 |
t.datetime "locked_at" |
135 |
- t.string "username", :null => false |
|
136 |
- t.string "invitation_code", :null => false |
|
139 |
+ t.string "username", null: false |
|
140 |
+ t.string "invitation_code", null: false |
|
137 | 141 |
t.integer "scenario_count", default: 0, null: false |
138 | 142 |
end |
139 | 143 |
|
140 |
- add_index "users", ["email"], :name => "index_users_on_email", :unique => true |
|
141 |
- add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true |
|
142 |
- add_index "users", ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true |
|
143 |
- add_index "users", ["username"], :name => "index_users_on_username", :unique => true |
|
144 |
+ add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree |
|
145 |
+ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree |
|
146 |
+ add_index "users", ["unlock_token"], name: "index_users_on_unlock_token", unique: true, using: :btree |
|
147 |
+ add_index "users", ["username"], name: "index_users_on_username", unique: true, using: :btree |
|
144 | 148 |
|
145 | 149 |
end |
@@ -0,0 +1,53 @@ |
||
1 |
+class AgentsExporter |
|
2 |
+ attr_accessor :options |
|
3 |
+ |
|
4 |
+ def initialize(options) |
|
5 |
+ self.options = options |
|
6 |
+ end |
|
7 |
+ |
|
8 |
+ # Filename should have no commas or special characters to support Content-Disposition on older browsers. |
|
9 |
+ def filename |
|
10 |
+ ((options[:name] || '').downcase.gsub(/[^a-z0-9_-]/, '-').gsub(/-+/, '-').gsub(/^-|-$/, '').presence || 'exported-agents') + ".json" |
|
11 |
+ end |
|
12 |
+ |
|
13 |
+ def as_json(opts = {}) |
|
14 |
+ { |
|
15 |
+ :name => options[:name].presence || 'No name provided', |
|
16 |
+ :description => options[:description].presence || 'No description provided', |
|
17 |
+ :source_url => options[:source_url], |
|
18 |
+ :guid => options[:guid], |
|
19 |
+ :exported_at => Time.now.utc.iso8601, |
|
20 |
+ :agents => agents.map { |agent| agent_as_json(agent) }, |
|
21 |
+ :links => links |
|
22 |
+ } |
|
23 |
+ end |
|
24 |
+ |
|
25 |
+ def agents |
|
26 |
+ options[:agents].to_a |
|
27 |
+ end |
|
28 |
+ |
|
29 |
+ def links |
|
30 |
+ agent_ids = agents.map(&:id) |
|
31 |
+ |
|
32 |
+ contained_links = agents.map.with_index do |agent, index| |
|
33 |
+ agent.links_as_source.where(:receiver_id => agent_ids).map do |link| |
|
34 |
+ { :source => index, :receiver => agent_ids.index(link.receiver_id) } |
|
35 |
+ end |
|
36 |
+ end |
|
37 |
+ |
|
38 |
+ contained_links.flatten.compact |
|
39 |
+ end |
|
40 |
+ |
|
41 |
+ def agent_as_json(agent) |
|
42 |
+ { |
|
43 |
+ :type => agent.type, |
|
44 |
+ :name => agent.name, |
|
45 |
+ :schedule => agent.schedule, |
|
46 |
+ :keep_events_for => agent.keep_events_for, |
|
47 |
+ :propagate_immediately => agent.propagate_immediately, |
|
48 |
+ :disabled => agent.disabled, |
|
49 |
+ :source_system_agent_id => agent.id, |
|
50 |
+ :options => agent.options |
|
51 |
+ } |
|
52 |
+ end |
|
53 |
+end |
@@ -32,6 +32,59 @@ describe ScenariosController do |
||
32 | 32 |
end |
33 | 33 |
end |
34 | 34 |
|
35 |
+ describe "GET share" do |
|
36 |
+ it "only displays Scenario share information for the current user" do |
|
37 |
+ get :share, :id => scenarios(:bob_weather).to_param |
|
38 |
+ assigns(:scenario).should eq(scenarios(:bob_weather)) |
|
39 |
+ |
|
40 |
+ lambda { |
|
41 |
+ get :share, :id => scenarios(:jane_weather).to_param |
|
42 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
43 |
+ end |
|
44 |
+ end |
|
45 |
+ |
|
46 |
+ describe "GET export" do |
|
47 |
+ it "returns a JSON file download from an instantiated AgentsExporter" do |
|
48 |
+ get :export, :id => scenarios(:bob_weather).to_param |
|
49 |
+ assigns(:exporter).options[:name].should == scenarios(:bob_weather).name |
|
50 |
+ assigns(:exporter).options[:description].should == scenarios(:bob_weather).description |
|
51 |
+ assigns(:exporter).options[:agents].should == scenarios(:bob_weather).agents |
|
52 |
+ assigns(:exporter).options[:guid].should == scenarios(:bob_weather).guid |
|
53 |
+ assigns(:exporter).options[:source_url].should be_false |
|
54 |
+ response.headers['Content-Disposition'].should == 'attachment; filename="bob-s-weather-alert-scenario.json"' |
|
55 |
+ response.headers['Content-Type'].should == 'application/json; charset=utf-8' |
|
56 |
+ JSON.parse(response.body)["name"].should == scenarios(:bob_weather).name |
|
57 |
+ end |
|
58 |
+ |
|
59 |
+ it "only exports private Scenarios for the current user" do |
|
60 |
+ get :export, :id => scenarios(:bob_weather).to_param |
|
61 |
+ assigns(:scenario).should eq(scenarios(:bob_weather)) |
|
62 |
+ |
|
63 |
+ lambda { |
|
64 |
+ get :export, :id => scenarios(:jane_weather).to_param |
|
65 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
66 |
+ end |
|
67 |
+ |
|
68 |
+ describe "public exports" do |
|
69 |
+ before do |
|
70 |
+ scenarios(:jane_weather).update_attribute :public, true |
|
71 |
+ end |
|
72 |
+ |
|
73 |
+ it "exports public scenarios for other users when logged in" do |
|
74 |
+ get :export, :id => scenarios(:jane_weather).to_param |
|
75 |
+ assigns(:scenario).should eq(scenarios(:jane_weather)) |
|
76 |
+ assigns(:exporter).options[:source_url].should == export_scenario_url(scenarios(:jane_weather)) |
|
77 |
+ end |
|
78 |
+ |
|
79 |
+ it "exports public scenarios for other users when logged out" do |
|
80 |
+ sign_out :user |
|
81 |
+ get :export, :id => scenarios(:jane_weather).to_param |
|
82 |
+ assigns(:scenario).should eq(scenarios(:jane_weather)) |
|
83 |
+ assigns(:exporter).options[:source_url].should == export_scenario_url(scenarios(:jane_weather)) |
|
84 |
+ end |
|
85 |
+ end |
|
86 |
+ end |
|
87 |
+ |
|
35 | 88 |
describe "GET edit" do |
36 | 89 |
it "only shows Scenarios for the current user" do |
37 | 90 |
get :edit, :id => scenarios(:bob_weather).to_param |
@@ -67,9 +120,10 @@ describe ScenariosController do |
||
67 | 120 |
|
68 | 121 |
describe "PUT update" do |
69 | 122 |
it "updates attributes on Scenarios for the current user" do |
70 |
- post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name" } |
|
123 |
+ post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1" } |
|
71 | 124 |
response.should redirect_to(scenario_path(scenarios(:bob_weather))) |
72 | 125 |
scenarios(:bob_weather).reload.name.should == "new_name" |
126 |
+ scenarios(:bob_weather).should be_public |
|
73 | 127 |
|
74 | 128 |
lambda { |
75 | 129 |
post :update, :id => scenarios(:jane_weather).to_param, :scenario => { :name => "new_name" } |
@@ -1,7 +1,13 @@ |
||
1 | 1 |
jane_weather: |
2 | 2 |
name: Jane's weather alert Scenario |
3 | 3 |
user: jane |
4 |
+ description: Jane's weather alert system |
|
5 |
+ public: false |
|
6 |
+ guid: random-guid-generated-by-bob |
|
4 | 7 |
|
5 | 8 |
bob_weather: |
6 | 9 |
name: Bob's weather alert Scenario |
7 | 10 |
user: bob |
11 |
+ description: Bob's weather alert system |
|
12 |
+ public: false |
|
13 |
+ guid: random-guid-generated-by-jane |
@@ -0,0 +1,58 @@ |
||
1 |
+# encoding: utf-8 |
|
2 |
+ |
|
3 |
+require 'spec_helper' |
|
4 |
+ |
|
5 |
+describe AgentsExporter do |
|
6 |
+ describe "#as_json" do |
|
7 |
+ let(:name) { "My set of Agents" } |
|
8 |
+ let(:description) { "These Agents work together nicely!" } |
|
9 |
+ let(:guid) { "some-guid" } |
|
10 |
+ let(:source_url) { "http://yourhuginn.com/scenarios/2/export.json" } |
|
11 |
+ let(:agent_list) { [agents(:jane_weather_agent), agents(:jane_rain_notifier_agent)] } |
|
12 |
+ let(:exporter) { AgentsExporter.new(:agents => agent_list, :name => name, :description => description, :source_url => source_url, :guid => guid) } |
|
13 |
+ |
|
14 |
+ it "outputs a structure containing name, description, the date, all agents & their links" do |
|
15 |
+ data = exporter.as_json |
|
16 |
+ data[:name].should == name |
|
17 |
+ data[:description].should == description |
|
18 |
+ data[:source_url].should == source_url |
|
19 |
+ data[:guid].should == guid |
|
20 |
+ Time.parse(data[:exported_at]).should be_within(2).of(Time.now.utc) |
|
21 |
+ data[:links].should == [{ :source => 0, :receiver => 1 }] |
|
22 |
+ data[:agents].should == agent_list.map { |agent| exporter.agent_as_json(agent) } |
|
23 |
+ data[:agents].all? { |agent_json| agent_json[:source_system_agent_id] && agent_json[:type] && agent_json[:name] }.should be_true |
|
24 |
+ end |
|
25 |
+ |
|
26 |
+ it "does not output links to other agents" do |
|
27 |
+ Link.create!(:source_id => agents(:jane_weather_agent).id, :receiver_id => agents(:jane_website_agent).id) |
|
28 |
+ Link.create!(:source_id => agents(:jane_website_agent).id, :receiver_id => agents(:jane_rain_notifier_agent).id) |
|
29 |
+ |
|
30 |
+ exporter.as_json[:links].should == [{ :source => 0, :receiver => 1 }] |
|
31 |
+ end |
|
32 |
+ end |
|
33 |
+ |
|
34 |
+ describe "#filename" do |
|
35 |
+ it "strips special characters" do |
|
36 |
+ AgentsExporter.new(:name => "ƏfooƐƕƺbar").filename.should == "foo-bar.json" |
|
37 |
+ end |
|
38 |
+ |
|
39 |
+ it "strips punctuation" do |
|
40 |
+ AgentsExporter.new(:name => "foo,bar").filename.should == "foo-bar.json" |
|
41 |
+ end |
|
42 |
+ |
|
43 |
+ it "strips leading and trailing dashes" do |
|
44 |
+ AgentsExporter.new(:name => ",foo,").filename.should == "foo.json" |
|
45 |
+ end |
|
46 |
+ |
|
47 |
+ it "has a default when options[:name] is nil" do |
|
48 |
+ AgentsExporter.new(:name => nil).filename.should == "exported-agents.json" |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ it "has a default when the result is empty" do |
|
52 |
+ AgentsExporter.new(:name => "").filename.should == "exported-agents.json" |
|
53 |
+ AgentsExporter.new(:name => "Ə").filename.should == "exported-agents.json" |
|
54 |
+ AgentsExporter.new(:name => "-").filename.should == "exported-agents.json" |
|
55 |
+ AgentsExporter.new(:name => ",,").filename.should == "exported-agents.json" |
|
56 |
+ end |
|
57 |
+ end |
|
58 |
+end |
@@ -26,6 +26,17 @@ describe Scenario do |
||
26 | 26 |
end |
27 | 27 |
end |
28 | 28 |
|
29 |
+ describe "guid" do |
|
30 |
+ it "gets created before_save, but only if it's not present" do |
|
31 |
+ scenario = users(:bob).scenarios.new(:name => "some scenario") |
|
32 |
+ scenario.guid.should be_nil |
|
33 |
+ scenario.save! |
|
34 |
+ scenario.guid.should_not be_nil |
|
35 |
+ |
|
36 |
+ lambda { scenario.save! }.should_not change { scenario.reload.guid } |
|
37 |
+ end |
|
38 |
+ end |
|
39 |
+ |
|
29 | 40 |
describe "counters" do |
30 | 41 |
before do |
31 | 42 |
@scenario = users(:bob).scenarios.new(:name => "some scenario") |